15-5 最佳实践数据库代码优化:创建userModule
模块化重构背景
问题分析
现有架构中用户相关数据库操作与通用数据库配置耦合,导致以下典型问题:
1. 代码职责不清晰
- 现象:用户CRUD操作分散在
database
和app
模块 - 案例:用户注册逻辑同时涉及:
// 错误示例:跨模块混合调用 class AppController { constructor( private userService: UserService, // 来自业务模块 private dbService: DatabaseService // 来自基础设施 ) {} }
typescript - 后果:修改用户表结构需要同时检查多个模块
2. 模块间依赖混乱
- 典型循环依赖:
- 真实场景:当
UserModule
需要扩展权限功能时,因循环依赖无法直接引入AuthModule
3. 可维护性降低
- 量化影响(基于业界数据):
指标 耦合架构 模块化架构 变更影响范围 5-8个文件 1-2个文件 新增功能耗时 2-3天 0.5-1天
优化目标
1. 分离业务逻辑与基础设施
- 实现方案:
- 基础设施层:仅包含数据库连接池、驱动配置
// database.module.ts @Module({ providers: [{ provide: 'DATA_SOURCE', useFactory: () => new DataSource(...) // 纯技术配置 }] })
typescript - 业务层:通过Repository模式访问数据
// user.repository.ts @Injectable() export class UserRepository { constructor( @Inject('DATA_SOURCE') private dataSource: DataSource ) {} findById(id: string) { // 业务语义明确的查询 return this.dataSource.query( `SELECT * FROM users WHERE id = $1`, [id] ) } }
typescript
- 基础设施层:仅包含数据库连接池、驱动配置
2. 实现功能模块自包含
- 模块标准结构:
user/ ├── user.module.ts # 模块声明 ├── user.controller.ts # 接口层 ├── user.service.ts # 业务逻辑 ├── user.repository.ts # 数据访问 └── entities/ └── user.entity.ts # 领域模型
text - 自包含验证:运行
nest build user
可独立编译
3. 建立清晰依赖关系链
- 依赖规则:
- 分层约束工具:使用ESLint规则禁止跨层调用
// .eslintrc { "rules": { "no-restricted-imports": [ "error", { "patterns": [ "src/*/application/*/infrastructure/*", "src/*/infrastructure/*/domain/*" ] } ] } }
json
行业实践对比
方案 | 适用场景 | 代表框架 |
---|---|---|
模块化 | 复杂业务系统 | NestJS, Spring |
一体化 | 快速原型开发 | Express, Flask |
微服务 | 超大规模系统 | Dapr, Service Mesh |
💡 深度提示:根据康威定律,代码结构应匹配团队组织结构。若用户系统由独立小组开发,强模块化是必选项。
创建user模块
使用Nest CLI生成模块
命令详解
nest g mo user --no-spec --flat
bash
g mo
:generate module的缩写user
:模块名称(建议使用业务领域命名)--no-spec
:不生成测试文件(适合快速原型阶段)--flat
:不创建子目录(适合简单模块)
执行效果
- 文件创建:
// src/user/user.module.ts import { Module } from '@nestjs/common'; @Module({}) export class UserModule {}
typescript - 自动注册:
// src/app.module.ts import { UserModule } from './user/user.module'; @Module({ imports: [UserModule], // 自动添加 }) export class AppModule {}
typescript
高级选项
参数 | 作用 | 适用场景 |
---|---|---|
--dry-run | 预览生成结果 | 安全验证 |
--project | 指定项目 | Monorepo |
--path | 自定义路径 | 特殊目录结构 |
模块结构调整
迁移步骤
- 控制器迁移:
nest g co user --no-spec --flat
bash- 自动生成基础控制器结构
- 更新路由前缀为
/user
- 服务迁移:
nest g s user --no-spec --flat
bash- 将原
app.service
中用户逻辑剪切至此
- 将原
- 清理原模块:
// src/app.controller.ts // 移除所有@Get('/user')等路由
typescript
依赖关系验证
结构调整对比
调整前 | 调整后 | 优势 |
---|---|---|
所有控制器在app | 按业务分离 | 降低认知负荷 |
共享数据库服务 | 模块自有Repository | 独立测试 |
全局路由前缀 | 模块级路由 | 路径更语义化 |
常见问题解决
Q1: 如何避免循环依赖?
- 方案:使用前向引用(forwardRef)
// user.module.ts @Module({ imports: [forwardRef(() => AuthModule)] })
typescript
Q2: 需要共享服务怎么办?
- 模式:
// shared.module.ts @Module({ providers: [CommonService], exports: [CommonService] })
typescript
Q3: 生成文件位置错误?
- 修正命令:
nest g mo modules/user --no-spec # 生成到src/modules/user目录
bash
最佳实践建议
- 命名规范:
- 模块:
[功能].module.ts
- 控制器:
[功能].controller.ts
- 服务:
[功能].service.ts
- 模块:
- 目录结构:
src/ ├── modules/ │ └── user/ │ ├── user.module.ts │ ├── user.controller.ts │ └── user.service.ts └── app.module.ts
text - 代码验证:
nest build --webpack --webpackPath webpack.config.js # 确保模块可独立编译
bash
💡 扩展思考:对于大型项目,可结合领域驱动设计(DDD),将user
模块升级为identity
限界上下文,包含认证、授权等子模块。
数据库配置迁移
TypeORM配置迁移
1. 迁移核心配置
// 迁移前 (typeorm-common.module.ts)
@Module({
imports: [
TypeOrmModule.forFeature([UserEntity, ProductEntity]) // 混合实体
],
exports: [TypeOrmModule]
})
// 迁移后 (user.module.ts)
@Module({
imports: [
TypeOrmModule.forFeature([UserEntity]) // 仅用户实体
]
})
typescript
2. 路径修正最佳实践
- 错误模式:
import { User } from '../../../shared/entities' // 相对路径混乱
typescript - 推荐方案:
// tsconfig.json 配置路径映射 { "compilerOptions": { "paths": { "@database/typeorm": ["src/infrastructure/database/typeorm"] } } }
typescript
3. 多数据库支持
// 支持多个数据源
TypeOrmModule.forFeature([UserEntity], 'user_db')
TypeOrmModule.forFeature([LogEntity], 'log_db')
typescript
Mongoose配置迁移
1. Schema规范
// database/mongodb/schemas/user.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
@Schema({ timestamps: true })
export class User {
@Prop({ required: true })
name: string;
@Prop({ index: true, unique: true })
email: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
typescript
2. 连接管理
// mongoose.module.ts (基础设施层)
@Module({
imports: [
MongooseModule.forRootAsync({
useFactory: () => ({
uri: process.env.MONGO_URI,
connectionFactory: (connection) => {
connection.plugin(require('mongoose-autopopulate'));
return connection;
}
})
})
]
})
typescript
3. 模块化注册
// user.module.ts
@Module({
imports: [
MongooseModule.forFeature([
{ name: User.name, schema: UserSchema }
])
]
})
typescript
Prisma配置迁移
1. 服务标准化
// 重命名后 (prisma.service.ts)
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
typescript
2. 多环境配置
# prisma/.env
DATABASE_URL="postgresql://user:password@localhost:5432/prod?schema=public"
TEST_DATABASE_URL="postgresql://user:password@localhost:5432/test?schema=public"
bash
3. 事务管理增强
// 在user.service.ts中使用
async createWithTransaction(userData: CreateUserDto) {
return this.prisma.$transaction(async (tx) => {
const user = await tx.user.create({ data: userData });
await tx.account.create({ data: { userId: user.id } });
return user;
});
}
typescript
迁移验证清单
项目 | 检查点 | 验证方法 |
---|---|---|
TypeORM | 实体注册正确 | SELECT * FROM pg_tables WHERE tablename = 'users' |
Mongoose | 索引生效 | db.users.getIndexes() |
Prisma | 连接池状态 | prisma.$queryRaw SELECT 1`` |
性能优化建议
- 连接池配置:
// TypeORM配置示例 extra: { max: 20, // 最大连接数 connectionTimeoutMillis: 5000 }
typescript - 索引优化:
// Mongoose Schema @Prop({ index: { unique: true, sparse: true } }) username: string;
typescript
错误处理模式
// 统一错误拦截
@Catch(Prisma.PrismaClientKnownRequestError)
export class PrismaErrorFilter implements ExceptionFilter {
catch(exception: PrismaClientKnownRequestError, host: ArgumentsHost) {
// 处理唯一约束冲突等错误
}
}
typescript
💡 扩展思考:对于多租户系统,可结合TYPEORM_MULTIPLE_DATABASES
环境变量实现动态数据源切换,每个租户独立Schema。
控制器重构
创建UserController的完整流程
1. 生成控制器
nest g co user --no-spec --flat
bash
这条命令会生成以下文件结构:
src/
└── user/
├── user.controller.ts
└── user.module.ts
text
2. 路由设计最佳实践
@Controller('api/v1/users') // 复数形式更符合REST规范
export class UserController {
constructor(
private readonly userService: UserService, // 通过Service层访问Repository
private readonly logger: LoggerService // 注入日志服务
) {}
@Get()
@HttpCode(200)
async findAll(
@Query() paginationDto: PaginationDto // 分页参数
) {
this.logger.log('Fetching users');
return this.userService.findAll(paginationDto);
}
@Post()
@HttpCode(201)
async create(
@Body() createUserDto: CreateUserDto // 数据验证
) {
return this.userService.create(createUserDto);
}
}
typescript
3. 版本控制方案
方案 | 实现方式 | 优点 |
---|---|---|
URI路径 | @Controller('v1/users') | 直观简单 |
请求头 | @Header('Api-Version', '1.0') | 更灵活 |
媒体类型 | @Post(produces: 'application/vnd.company.v1+json') | 严格规范 |
依赖注入深度优化
1. 分层注入架构
2. 作用域控制
// 限定为请求级实例
@Injectable({ scope: Scope.REQUEST })
export class UserRepository {
constructor(
@InjectConnection() private connection: Connection
) {}
}
typescript
3. 动态提供者
// user.module.ts
const repositoryProvider = {
provide: 'USER_REPOSITORY',
useFactory: (config: ConfigService) => {
return config.get('DB_TYPE') === 'mongodb'
? new MongoUserRepository()
: new PgUserRepository();
},
inject: [ConfigService]
};
typescript
安全增强措施
1. 输入验证
// create-user.dto.ts
import { IsEmail, IsStrongPassword } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsStrongPassword({
minLength: 8,
minUppercase: 1
})
password: string;
}
typescript
2. 速率限制
@Throttle(100, 60) // 每分钟100次
@Get('search')
async searchUsers(@Query() query: string) {
// ...
}
typescript
性能优化技巧
1. 缓存集成
@Get(':id')
@CacheTTL(30) // 30秒缓存
async findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}
typescript
2. 响应压缩
// main.ts
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn'],
snapshot: true,
});
app.use(compression()); // 启用gzip
typescript
测试策略
1. 单元测试示例
describe('UserController', () => {
let controller: UserController;
let mockService: jest.Mocked<UserService>;
beforeEach(async () => {
mockService = {
findAll: jest.fn().mockResolvedValue([testUser]),
} as any;
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{ provide: UserService, useValue: mockService }
],
}).compile();
controller = module.get<UserController>(UserController);
});
it('应该返回用户列表', async () => {
await expect(controller.findAll({})).resolves.toEqual([testUser]);
expect(mockService.findAll).toBeCalledTimes(1);
});
});
typescript
2. 测试覆盖率目标
组件 | 覆盖率要求 |
---|---|
控制器 | ≥80% |
服务层 | ≥90% |
仓库层 | ≥95% |
生产环境建议
- 健康检查:
@Get('health') @HealthCheck() healthCheck() { return this.httpHealthIndicator.pingCheck('user-service'); }
typescript - 文档生成:
npm run swagger # 自动生成API文档
bash - 监控指标:
@Get('metrics') @UseInterceptors(MetricsInterceptor) getMetrics() { return this.metricsService.getMetrics(); }
typescript
💡 架构演进:当业务复杂时,可考虑将控制器拆分为:
UserQueryController
(处理GET请求)UserCommandController
(处理POST/PUT/DELETE) 遵循CQRS模式分离读写职责
分层架构规范
架构演进全景图
详细职责矩阵
层级 | 核心职责 | 技术实现 | 验证指标 | 典型文件 |
---|---|---|---|---|
接口层 | 请求处理/响应格式化 | 控制器/拦截器 | API测试覆盖率≥85% | user.controller.ts |
应用层 | 业务流程编排 | 服务/用例 | 单元测试覆盖率≥90% | create-user.service.ts |
领域层 | 业务规则实现 | 实体/值对象 | 领域测试覆盖率≥95% | user.entity.ts |
基础设施层 | 技术细节实现 | 仓库/适配器 | 集成测试通过率100% | typeorm-user.repository.ts |
现代化目录结构
src/
├── core/ # 核心领域
│ ├── users/ # 用户子域
│ │ ├── application/ # 应用服务
│ │ ├── domain/ # 领域模型
│ │ └── infrastructure/ # 技术实现
├── infrastructure/
│ ├── database/ # 数据库配置
│ └── shared/ # 跨领域服务
└── interfaces/
├── rest/ # HTTP接口
└── grpc/ # gRPC接口
text
分层通信规范
分层开发约束
- 严格分层规则:
// eslintrc.js rules: { "no-restricted-imports": [ "error", { paths: [ { name: "interfaces", message: "禁止接口层直接导入基础设施" } ] } ] }
typescript - 依赖注入规范:
// 正确示例 @Injectable() export class UserService { constructor( private readonly repository: UserRepository, // 领域接口 @Inject('EmailService') private emailService // 抽象服务 ) {} }
typescript
性能优化分层策略
层级 | 优化手段 | 工具示例 |
---|---|---|
接口层 | 响应缓存/压缩 | Redis/Compression |
应用层 | 批处理/CQRS | Bull/Mediatr |
领域层 | 延迟加载 | TypeORM懒加载 |
基础设施 | 连接池 | HikariCP |
微服务演进路径
常见问题解决方案
Q1:如何避免领域层污染?
- 方案:使用防腐层(ACL)
// infrastructure/adapters/user.adapter.ts export class UserAdapter { static toDomain(persistence: UserEntity): User { return User.create({ ...persistence }); } }
typescript
Q2:跨层共享类型怎么办?
- 模式:建立共享类型库
// shared-kernel/types/user.ts export type UserEssential = Pick<User, 'id' | 'name'>;
typescript
Q3:基础设施切换成本高?
- 策略:依赖抽象+适配器
// domain/repositories/user.repository.interface.ts export interface IUserRepository { findById(id: string): Promise<User>; // 抽象方法定义 }
typescript
演进度量指标
架构阶段 | 模块内聚度 | 耦合度 | 部署频率 |
---|---|---|---|
单体架构 | 30% | 70% | 每周1次 |
模块化 | 65% | 35% | 每日1次 |
领域驱动 | 85% | 15% | 每日多次 |
💡 架构师提示:建议使用SonarQube持续监控分层质量指标,当模块间调用超过3层时触发架构评审。
测试验证
启动应用验证
启动命令详解
npm run start:dev
bash
- 底层机制:触发NestCLI的
webpack-hmr
热更新流程 - 关键日志:
[Nest] 12345 - 2023/08/15 10:00:00 [NestFactory] Starting Nest application... [Nest] 12345 - 2023/08/15 10:00:02 [InstanceLoader] UserModule dependencies initialized
text
健康检查清单
检查项 | 验证方法 | 预期结果 |
---|---|---|
模块加载 | 观察启动日志 | 无Circular dependency 警告 |
数据库连接 | 检查ORM日志 | Connection established |
配置加载 | 注入ConfigService测试 | 能读取.env变量 |
初始化异常处理
// main.ts 增强错误捕获
async function bootstrap() {
try {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
} catch (err) {
process.exit(1);
}
}
typescript
接口自动化测试
测试框架配置
// test/jest-e2e.config.js
module.exports = {
preset: 'jest',
testEnvironment: 'node',
testRegex: '.e2e-spec.ts$',
setupFiles: ['<rootDir>/test/setup-env.ts']
}
javascript
多数据库验证测试
// test/user/user.controller.e2e-spec.ts
describe('UserController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
test('/user/mongo (GET)', () => {
return request(app.getHttpServer())
.get('/api/v1/user/mongo')
.expect(200)
.expect(res => {
expect(res.body).toHaveProperty('data');
expect(res.body.data).toBeInstanceOf(Array);
});
});
// 其他接口测试...
});
typescript
数据库断言策略
数据库类型 | 验证方法 | 示例断言 |
---|---|---|
MongoDB | 检查ObjectID格式 | expect.objectContaining({ _id: expect.any(String) }) |
TypeORM | 验证实体结构 | toMatchObject({ createdAt: expect.any(String) }) |
Prisma | 检查关系数据 | toHaveProperty('profile.email') |
性能基准测试
压力测试配置
# 使用k6进行负载测试
k6 run --vus 100 --duration 30s test/load/user.js
bash
性能验收标准
接口 | 平均响应时间 | 错误率 | QPS |
---|---|---|---|
/mongo | ≤300ms | <0.5% | ≥500 |
/typeorm | ≤200ms | <0.2% | ≥800 |
/prisma | ≤150ms | <0.1% | ≥1000 |
测试数据管理
数据工厂示例
// test/factories/user.factory.ts
export const mockUser = (overrides?: Partial<User>) => ({
name: '测试用户',
email: 'test@example.com',
...overrides
});
typescript
测试生命周期
持续集成集成
GitHub Actions配置
# .github/workflows/test.yml
jobs:
test:
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: test
mongo:
image: mongo:5
steps:
- run: npm test
- run: k6 run test/load/user.js
yaml
常见问题排查
Q1:跨数据库测试失败?
- 解决方案:
// 在测试模块中动态切换提供者 const dbType = process.env.TEST_DB_TYPE; providers: [ { provide: UserRepository, useClass: dbType === 'mongo' ? MongoUserRepository : PgUserRepository } ]
typescript
Q2:测试数据污染?
- 隔离方案:
beforeEach(async () => { await testTransaction.start(); }); afterEach(async () => { await testTransaction.rollback(); });
typescript
Q3:CI环境超时?
- 优化配置:
// jest.config.js testTimeout: process.env.CI ? 10000 : 5000
javascript
💡 测试金字塔实践:建议保持单元测试:集成测试:e2e测试 ≈ 70:20:10 的比例,对核心业务模块增加契约测试(Pact)验证接口兼容性。
最佳实践总结
模块化设计思想深度解析
1. 高内聚实现模式
// 用户模块完整结构
@Module({
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [UserController],
providers: [
UserService,
{ provide: 'UserRepository', useClass: TypeORMUserRepository }
],
exports: [UserService] // 仅暴露必要服务
})
export class UserModule {}
typescript
典型特征:
- 包含用户相关实体、控制器、服务
- 仓储实现细节对模块外不可见
- 通过
exports
控制暴露边界
2. 低耦合通信方案
3. 依赖倒置实战
// 领域层定义
export interface IUserRepository {
findById(id: string): Promise<User>;
}
// 基础设施实现
@Injectable()
export class MongoUserRepository implements IUserRepository {
// 具体实现...
}
// 业务模块配置
{
provide: IUserRepository,
useClass: process.env.DB_TYPE === 'mongo'
? MongoUserRepository
: PgUserRepository
}
typescript
场景化解决方案库
1. 单一数据库优化方案
// app.module.ts 简化配置
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.DATABASE_URL,
autoLoadEntities: true // 自动加载实体
})
]
})
typescript
2. 多租户动态数据源
// tenant.module.ts
@Module({
providers: [
{
provide: getDataSourceToken(),
useFactory: (config: TenantConfig) => {
return new DataSource({
type: 'postgres',
url: config.tenantDbUrl,
entities: [User, Product]
});
},
inject: [TenantConfig]
}
]
})
typescript
3. 混合数据库协调器
// user.module.ts
@Module({
imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
TypeOrmModule.forFeature([UserLogEntity])
],
providers: [UserSyncService] // 双写协调服务
})
typescript
复杂度评估矩阵
维度 | 单一数据库 | 多租户系统 | 混合数据库 |
---|---|---|---|
配置复杂度 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
维护成本 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
扩展性 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
团队技能要求 | ⭐ | ⭐⭐ | ⭐⭐⭐ |
架构决策检查表
- 模块边界验证:
- 能否用一句话描述模块核心职责?
- 模块间是否存在循环依赖?
- 技术选型评估:
- 是否所有服务都通过抽象接口访问?
- 数据库连接池配置是否隔离?
- 演进准备度:
- 模块能否独立打包部署?
- 是否具备多数据源切换能力?
反模式警示
企业级扩展建议
- 垂直拆分路线:
src/ ├── modules/ │ ├── user/ │ │ └── user.module.ts │ └── product/ │ └── product.module.ts └── shared/ ├── kernel/ └── infrastructure/
text - 水平扩展方案:
// 动态模块注册 @Module({}) export class DomainModule { static register(options: ModuleOptions): DynamicModule { return { module: DomainModule, imports: options.dbType === 'mongo' ? [MongoConfigModule] : [TypeORMConfigModule] }; } }
typescript
效能度量指标
指标项 | 合格阈值 | 测量工具 |
---|---|---|
模块内聚度 | ≥0.7 | SonarQube |
模块耦合度 | ≤0.3 | Dependency-Cruiser |
接口变更频率 | ≤2次/月 | Git History |
构建隔离度 | 100% | Nx Affected |
💡 架构师备忘录:当团队规模超过10人时,建议采用Monorepo+Module Federation实现模块自治与协同开发的平衡,使用Nx或Turborepo管理项目拓扑关系。
↑